package tazmania.entity;

import java.nio.FloatBuffer;
import java.nio.IntBuffer;

import javax.media.opengl.GL;
import javax.media.opengl.GL2;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.glu.gl2.GLUgl2;

import tazadum.engine.Entity;
import tazadum.engine.Interval;
import tazadum.engine.Signal;
import tazadum.engine.math.SimplexNoise;
import tazadum.engine.math.Vector;
import tazadum.engine.trigger.LeakyIntegrator;
import tazadum.engine.util.BufferUtil;

public class WireframeLandscapeEntity implements Entity {
	private static final int WIDTH = 100;
	private static final int HEIGHT = 50;

	private final Signal signal;

	private IntBuffer indices;
	private FloatBuffer vertices;
	private FloatBuffer colors;

	private final int polygons = (WIDTH - 1) * (HEIGHT - 1) * 2;

	protected GLUgl2 glu = new GLUgl2();

	protected final Vector up = Vector.Y;
	protected final Vector eye = new Vector(10, 10, 10);
	protected final Vector target = new Vector(0, 0, 0);

	private final int noiseChannel;
	private final int animationChannel;
	private final int heightChannel;

	private final LeakyIntegrator heightAdapter = new LeakyIntegrator(.4f);
	private final LeakyIntegrator leakyNoise = new LeakyIntegrator(3);

	public WireframeLandscapeEntity(final Signal signal, final int noiseChannel, final int animationChannel, final int heightChannel) {
		this.signal = signal;
		this.noiseChannel = noiseChannel;
		this.animationChannel = animationChannel;
		this.heightChannel = heightChannel;
	}

	@Override
	public void precalc(final GLAutoDrawable drawable, final float screenRatio) {
		colors = BufferUtil.newFloatBuffer(3 * WIDTH * HEIGHT);
		colors.rewind();

		vertices = BufferUtil.newFloatBuffer(3 * WIDTH * HEIGHT);
		vertices.rewind();

		for (int z = 0; z < HEIGHT; z++) {
			for (int x = 0; x < WIDTH; x++) {
				final float xpos = 1f * (x - WIDTH / 2);
				final float ypos = 1f * 0;
				final float zpos = 1f * z;

				vertices.put(xpos);
				vertices.put(ypos);
				vertices.put(zpos);

				colors.put(1);
				colors.put(1);
				colors.put(1);
			}
		}
		vertices.rewind();
		colors.rewind();

		indices = BufferUtil.newIntBuffer(3 * polygons);
		indices.rewind();
		for (int z = 0; z < HEIGHT - 1; z++) {
			for (int x = 0; x < WIDTH - 1; x++) {
				final int index = x + z * WIDTH;
				indices.put(index);
				indices.put(index + 1 + WIDTH);
				indices.put(index + 1);

				indices.put(index);
				indices.put(index + WIDTH);
				indices.put(index + 1 + WIDTH);
			}
		}
		indices.rewind();
	}

	@Override
	public void update(final GLAutoDrawable drawable, final float globalTime, final float localTime, final float dt, final Interval interval) {
		final GL2 gl = drawable.getGL().getGL2();

		gl.glMatrixMode(GL2.GL_PROJECTION);
		gl.glPushMatrix();
		glu.gluPerspective(110, 1, 0.1f, 1000);

		gl.glMatrixMode(GL2.GL_MODELVIEW);
		gl.glPushMatrix();
		gl.glLoadIdentity();

		final float time = localTime * 5;
		final float frac = time - (int) time;

		final float noiseValue = leakyNoise.apply(dt, signal.channel(noiseChannel) * 10);
		final float animationValue = signal.channel(animationChannel);
		final float heigtValue = Math.min(1, heightAdapter.apply(dt, signal.channel(heightChannel) * 15));

		final float dx = (float) (noiseValue * (Math.random() - 0.5)) * 0.5f;
		final float dy = (float) (noiseValue * (Math.random() - 0.5)) * 0.5f;

		eye.x = 0.5f + dx;
		eye.y = 2 + dy;
		eye.z = time;
		target.x = eye.x;
		target.z = eye.z + 1;
		target.y = eye.y;
		glu.gluLookAt(eye.x, eye.y, eye.z, target.x, target.y, target.z, Vector.Y.x, Vector.Y.y, Vector.Y.z);

		final float R = signal.channel(Signal.CHANNEL_R);
		final float G = signal.channel(Signal.CHANNEL_G);
		final float B = signal.channel(Signal.CHANNEL_B);

		gl.glEnable(GL.GL_DEPTH_TEST);
		gl.glEnable(GL.GL_LINE_SMOOTH);
		gl.glEnable(GL.GL_CULL_FACE);
		gl.glEnable(GL2.GL_FOG);

		gl.glCullFace(GL.GL_BACK);
		gl.glHint(GL.GL_LINE_SMOOTH_HINT, GL.GL_NICEST);
		gl.glShadeModel(GL2.GL_FLAT);
		gl.glFogi(GL2.GL_FOG_MODE, GL2.GL_EXP2);
		gl.glFogfv(GL2.GL_FOG_COLOR, new float[] { R, G, B, 1 }, 0);
		gl.glFogf(GL2.GL_FOG_DENSITY, .025f);

		// render
		gl.glEnableClientState(GL2.GL_VERTEX_ARRAY);
		gl.glVertexPointer(3, GL2.GL_FLOAT, 0, vertices);
		gl.glEnableClientState(GL2.GL_COLOR_ARRAY);
		gl.glColorPointer(3, GL.GL_FLOAT, 0, colors);

		gl.glColor3f(1f, 1f, 1f);
		gl.glPolygonMode(GL.GL_FRONT_AND_BACK, GL2.GL_FILL);
		gl.glDrawElements(GL.GL_TRIANGLES, polygons * 3, GL.GL_UNSIGNED_INT, indices);

		gl.glDisableClientState(GL2.GL_COLOR_ARRAY);
		gl.glLineWidth(.5f);
		gl.glColor3f(.5f, .5f, .5f);
		gl.glPolygonMode(GL.GL_FRONT_AND_BACK, GL2.GL_LINE);
		gl.glDrawElements(GL.GL_TRIANGLES, polygons * 3, GL.GL_UNSIGNED_INT, indices);

		// shift
		final float[] color = { 1, 1, 1 };
		vertices.rewind();
		colors.rewind();
		final int itime = (int) time;
		for (int z = 0; z < HEIGHT; z++) {
			final float zpos = z + itime;
			final float zHeight = Math.min((z - frac) * 0.2f, 4);
			for (int x = 0; x < WIDTH; x++) {
				final float height = Math.abs(x - WIDTH / 2) * 0.3f + zHeight + animationValue;
				final float noise = noise(x, z + animationValue, time);
				float ypos = noise * height * heigtValue;
				if (noiseValue > 0) {
					ypos += SimplexNoise.noise(x, z + itime, time) * noiseValue * 2f;
				}
				final int index = x + z * WIDTH;
				vertices.put(index * 3 + 1, ypos);
				vertices.put(index * 3 + 2, zpos);

				final float value = 0.7f + 0.3f * animationValue - 0.6f * noise(2 + x, 2 + z + animationValue, time);
				color[0] = value;
				color[1] = value;
				color[2] = value;
				colors.put(color, 0, 3);
			}
		}
		vertices.rewind();
		colors.rewind();

		gl.glDisableClientState(GL2.GL_VERTEX_ARRAY);

		gl.glDisable(GL.GL_LINE_SMOOTH);
		gl.glDisable(GL.GL_CULL_FACE);
		gl.glDisable(GL2.GL_LIGHT1);
		gl.glDisable(GL2.GL_FOG);

		// gl.glMatrixMode(GL2.GL_MODELVIEW);
		gl.glPopMatrix();
		gl.glMatrixMode(GL2.GL_PROJECTION);
		gl.glPopMatrix();
	}

	private float noise(final float x, final float y, final float animation) {
		return (float) SimplexNoise.noise(x * .1f, (y + (int) animation) * .1f, 0.01f * animation);
	}

	@Override
	public void destroy(final GLAutoDrawable drawable) {
	}
}
